'    Copyright 2008 Jan Molnar <jan.molnar@abds.de>
'
'    This file is part of JCQ.

'    JCQ is free software: you can redistribute it and/or modify
'    it under the terms of the GNU General Public License as published by
'    the Free Software Foundation, either version 2 of the License, or
'    (at your option) any later version.

'    JCQ is distributed in the hope that it will be useful,
'    but WITHOUT ANY WARRANTY; without even the implied warranty of
'    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
'    GNU General Public License for more details.

'    You should have received a copy of the GNU General Public License
'    along with JCQ. If not, see <http://www.gnu.org/licenses/>.

Public Class IcqUserInformationService
    Inherits ContextService
    Implements Interfaces.IUserInformationService

    Private Shared ReadOnly _PendingRequests As New Dictionary(Of Long, ShortUserInformationRequestManager)

    Friend Event ShortUserInformationReceived As EventHandler(Of ShortUserInformationTransportEventArgs)

    Public Sub New(ByVal context As Interfaces.IContext)
        MyBase.New(context)

        Dim connector As IcqConnector = TryCast(context.GetService(Of Interfaces.IConnector)(), IcqConnector)

        If connector Is Nothing Then Throw New InvalidCastException("Context Connector Service must be of Type IcqConnector")

        connector.RegisterSnacHandler(Of DataTypes.Snac010F)(&H1, &HF, New Action(Of DataTypes.Snac010F)(AddressOf AnalyseSnac010F))
        connector.RegisterSnacHandler(Of DataTypes.Snac030B)(&H3, &HB, New Action(Of DataTypes.Snac030B)(AddressOf AnalyseSnac030B))
        connector.RegisterSnacHandler(Of DataTypes.Snac030C)(&H3, &HC, New Action(Of DataTypes.Snac030C)(AddressOf AnalyseSnac030C))
        connector.RegisterSnacHandler(Of DataTypes.Snac1503)(&H15, &H3, New Action(Of DataTypes.Snac1503)(AddressOf AnalyseSnac1503))
    End Sub

    Public Sub RequestShortUserInfo(ByVal contact As Interfaces.IContact) Implements Interfaces.IUserInformationService.RequestShortUserInfo
        RequestShortUserInfo(contact, False)
    End Sub

    Public Sub RequestShortUserInfo(ByVal contact As Interfaces.IContact, ByVal force As Boolean) Implements Interfaces.IUserInformationService.RequestShortUserInfo
        Dim icqContact As IcqContact = TryCast(contact, IcqContact)
        Dim manager As ShortUserInformationRequestManager

        If icqContact Is Nothing Then Return

        If Not force And icqContact.LastShortUserInfoRequest > DateTime.MinValue Then Return

        manager = New ShortUserInformationRequestManager(Context, icqContact)
        manager.Execute()

        SyncLock _PendingRequests
            _PendingRequests.Add(manager.RequestId, manager)
        End SyncLock

        Core.Kernel.Logger.Log("IcqUserInformationService", TraceEventType.Error, "Request Manager created for request {0}", manager.RequestId)
    End Sub

    Public Sub RequestShortUserInfoForAllUsers() Implements Interfaces.IUserInformationService.RequestShortUserInfoForAllUsers
        Dim contacts As List(Of Interfaces.IContact)

        ' a copy of the actual list is required since the contact
        ' list may change during the following operation causing the
        ' enumeration to fail.
        contacts = Context.GetService(Of Interfaces.IStorageService).Contacts.ToList

        For Each x As Interfaces.IContact In contacts
            RequestShortUserInfo(x)

            ' sleep for 1 second to avoid server spaming
            ' which may result in a disconnect.
            System.Threading.Thread.Sleep(1000)
        Next

        RaiseEvent RequestShortUserInfoForAllUsersCompleted(Me, EventArgs.Empty)
    End Sub

    Friend Sub AnalyseSnac010F(ByVal dataIn As DataTypes.Snac010F)
        For Each x As DataTypes.UserInfo In dataIn.UserInfos
            Dim c As Interfaces.IContact = Context.GetService(Of Interfaces.IStorageService).GetContactByIdentifier(x.Uin)
            If c Is Nothing Then Continue For

            If x.MemberSince.HasData Then
                c.MemberSince = x.MemberSince.MemberSince
            End If

            If x.OnlineTime.HasData Then

            End If

            If x.SignOnTime.HasData Then
                c.SignOnTime = x.SignOnTime.SignOnTime
            End If

            If x.UserCapabilities.HasData Then

            End If

            If x.UserClass.HasData Then

            End If

            If x.UserIconIdAndHash.HasData Then
                c.SetIconHash(x.UserIconIdAndHash.IconMD5Hash)

                Debug.WriteLine(String.Format("User {0} has an Icon.", c.Identifier), "IcqUserInformationService")
            End If

            If x.UserStatus.HasData Then
                Dim oldStatus As Interfaces.IStatusCode
                Dim newStatus As Interfaces.IStatusCode

                oldStatus = c.Status
                newStatus = IcqStatusCodes.GetStatusCode(x.UserStatus.UserStatus)

                Debug.WriteLine(String.Format("User {0} changed Status to {1}.", c.Identifier, newStatus), "IcqUserInformationService")

                If oldStatus IsNot newStatus Then
                    c.Status = newStatus

                    'RaiseEvent ContactStatusChanged(Me, New Interfaces.StatusChangedEventArgs(oldStatus, newStatus, c))
                End If
            Else
                Dim oldStatus As Interfaces.IStatusCode
                Dim newStatus As Interfaces.IStatusCode

                oldStatus = c.Status
                newStatus = IcqStatusCodes.Online

                If oldStatus IsNot newStatus Then
                    c.Status = newStatus

                    'RaiseEvent ContactStatusChanged(Me, New Interfaces.StatusChangedEventArgs(oldStatus, newStatus, c))
                End If
            End If
        Next
    End Sub

    Friend Sub AnalyseSnac030B(ByVal dataIn As DataTypes.Snac030B)
        For Each x As DataTypes.UserInfo In dataIn.UserInfos
            Dim c As Interfaces.IContact = Context.GetService(Of Interfaces.IStorageService).GetContactByIdentifier(x.Uin)
            If c Is Nothing Then Continue For

            If x.MemberSince.HasData Then
                c.MemberSince = x.MemberSince.MemberSince
            End If

            If x.OnlineTime.HasData Then

            End If

            If x.SignOnTime.HasData Then
                c.SignOnTime = x.SignOnTime.SignOnTime
            End If

            If x.UserCapabilities.HasData Then

            End If

            If x.UserClass.HasData Then

            End If

            If x.UserIconIdAndHash.HasData Then
                c.SetIconHash(x.UserIconIdAndHash.IconMD5Hash)

                Debug.WriteLine(String.Format("User {0} has an Icon.", c.Identifier), "IcqUserInformationService")
            End If

            If x.UserStatus.HasData Then
                Dim oldStatus As Interfaces.IStatusCode
                Dim newStatus As Interfaces.IStatusCode

                oldStatus = c.Status
                newStatus = IcqStatusCodes.GetStatusCode(x.UserStatus.UserStatus)

                Debug.WriteLine(String.Format("User {0} changed Status to {1}.", c.Identifier, newStatus), "IcqUserInformationService")

                If oldStatus IsNot newStatus Then
                    c.Status = newStatus

                    'RaiseEvent ContactStatusChanged(Me, New Interfaces.StatusChangedEventArgs(oldStatus, newStatus, c))
                End If
            Else
                Dim oldStatus As Interfaces.IStatusCode
                Dim newStatus As Interfaces.IStatusCode

                oldStatus = c.Status
                newStatus = IcqStatusCodes.Online

                If oldStatus IsNot newStatus Then
                    c.Status = newStatus

                    'RaiseEvent ContactStatusChanged(Me, New Interfaces.StatusChangedEventArgs(oldStatus, newStatus, c))
                End If
            End If
        Next
    End Sub

    Friend Sub AnalyseSnac030C(ByVal dataIn As DataTypes.Snac030C)
        For Each x As DataTypes.UserInfo In dataIn.UserInfos
            Dim c As Interfaces.IContact = Context.GetService(Of Interfaces.IStorageService).GetContactByIdentifier(x.Uin)

            c.Status = IcqStatusCodes.GetStatusCode(DataTypes.UserStatus.Offline)
        Next
    End Sub

    Friend Sub AnalyseSnac1503(ByVal dataIn As DataTypes.Snac1503)
        Try
            If dataIn.MetaData.MetaResponse.ResponseType = DataTypes.MetaResponseType.MetaInformationResponse Then
                If DirectCast(dataIn.MetaData.MetaResponse, DataTypes.MetaInformationResponse).ResponseSubType = DataTypes.MetaResponseSubType.ShortUserInformationReply Then
                    Dim resp As DataTypes.MetaShortUserInformationResponse = DirectCast(dataIn.MetaData.MetaResponse, DataTypes.MetaShortUserInformationResponse)

                    RaiseEvent ShortUserInformationReceived(Me, New ShortUserInformationTransportEventArgs(dataIn.RequestId, resp))

                    SyncLock _PendingRequests
                        If _PendingRequests.ContainsKey(dataIn.RequestId) Then
                            _PendingRequests(dataIn.RequestId).ProcessResponse(resp)
                            _PendingRequests.Remove(dataIn.RequestId)
                        Else
                            Core.Kernel.Logger.Log("IcqUserInformationService", TraceEventType.Error, "No Request Manager for request {0}", dataIn.RequestId)
                        End If
                    End SyncLock
                End If
            End If
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub

    Public Event RequestShortUserInfoForAllUsersCompleted(ByVal sender As Object, ByVal e As System.EventArgs) Implements Interfaces.IUserInformationService.RequestShortUserInfoForAllUsersCompleted
End Class

Public Class ShortUserInformationTransportEventArgs
    Inherits System.EventArgs

    Public Sub New(ByVal requestId As Long, ByVal response As DataTypes.MetaShortUserInformationResponse)
        _RequestId = requestId
        _MetaResponse = response
    End Sub

    Private ReadOnly _MetaResponse As DataTypes.MetaShortUserInformationResponse
    Public ReadOnly Property MetaResponse() As DataTypes.MetaShortUserInformationResponse
        Get
            Return _MetaResponse
        End Get
    End Property

    Private ReadOnly _RequestId As Long
    Public ReadOnly Property RequestId() As Long
        Get
            Return _RequestId
        End Get
    End Property
End Class

Public Class ShortUserInformationRequestManager
    Private ReadOnly _Context As Interfaces.IContext
    Private ReadOnly _Contact As IcqContact

    Private _RetryTimer As System.Threading.Timer
    Private _RetryIteration As Integer

    Private _RequestSucceeded As Boolean
    Private _RequestId As Long

    Private _RetryDueMillisecond As Integer = Convert.ToInt32(TimeSpan.FromSeconds(10).TotalMilliseconds)

    Public Sub New(ByVal context As Interfaces.IContext, ByVal contact As IcqContact)
        _Context = context
        _Contact = contact
    End Sub

    Public ReadOnly Property Contact() As IcqContact
        Get
            Return _Contact
        End Get
    End Property

    Public ReadOnly Property Context() As Interfaces.IContext
        Get
            Return _Context
        End Get
    End Property

    Public ReadOnly Property RequestId() As Long
        Get
            Return _RequestId
        End Get
    End Property

    Public Sub ProcessResponse(ByVal response As DataTypes.MetaShortUserInformationResponse)
        _RequestSucceeded = True

        If response.SearchSucceeded Then
            Contact.Name = response.Nickname
            Contact.FirstName = response.FirstName
            Contact.LastName = response.LastName
            Contact.EmailAddress = response.EmailAddress
            Contact.Gender = DirectCast(response.Gender, Interfaces.ContactGender)
            Contact.AuthorizationRequired = response.AuthorizationRequired
            Contact.LastShortUserInfoRequest = Date.Now

            Core.Kernel.Logger.Log("IcqUserInformationService", TraceEventType.Information, "RequestId: {0}; '{1}' ({2}) FirstName: {3}, LastName: {4}, Auth Req. {5}", RequestId, Contact.Name, Contact.Identifier, Contact.FirstName, Contact.LastName, Contact.AuthorizationRequired)
        Else
            Core.Kernel.Logger.Log("IcqUserInformationService", TraceEventType.Error, "RequestId: {0}; Lookup for contact '{1}' ({2}) failed.", RequestId, Contact.Name, Contact.Identifier)
        End If
    End Sub

    Private Sub OnTimerCallback(ByVal state As Object)
        Try
            If Not _RequestSucceeded And _RetryIteration < 5 Then
                _RetryIteration += 1
                SendRequest()
            Else
                _RetryTimer.Dispose()
            End If
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub

    Private Sub SendRequest()
        Dim dataOut As DataTypes.Snac1502
        Dim req As DataTypes.MetaShortUserInformationRequest

        dataOut = New DataTypes.Snac1502

        req = New DataTypes.MetaShortUserInformationRequest
        req.RequestSequenceNumber = DataTypes.MetaRequest.GetNextSequenceNumber
        req.ClientUin = CLng(_Context.Identity.Identifier)
        req.SearchUin = CInt(Contact.Identifier)

        dataOut.MetaData.MetaRequest = req

        Dim transfer As IIcqDataTranferService = DirectCast(_Context.GetService(Of Interfaces.IConnector)(), IIcqDataTranferService)
        transfer.Send(dataOut)

        _RequestId = dataOut.RequestId
    End Sub

    Public Sub Execute()
        SendRequest()

        _RetryTimer = New System.Threading.Timer(New System.Threading.TimerCallback(AddressOf OnTimerCallback), Nothing, 0, _RetryDueMillisecond)
    End Sub
End Class